package com.ehome.plugin

import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

abstract class EHomeApmTask : DefaultTask() {
    private val TAG = this.javaClass.simpleName

    @get:Input
    abstract val apmExtension: Property<EHomeApmExtension>

    @get:Input
    abstract val variantExtension: Property<VariantDslExtension>

    @get:InputFiles
    abstract val allJars: ListProperty<RegularFile>

    @get:InputFiles
    abstract val allDirectories: ListProperty<Directory>

    @get:OutputFile
    abstract val output: RegularFileProperty

    @Internal
    val jarPaths = mutableSetOf<String>()

    @TaskAction
    fun taskAction() {
        println("$TAG apm blackList -> ${apmExtension.get().blackList}")

        val jarOutput = JarOutputStream(BufferedOutputStream(FileOutputStream(output.get().asFile)))

        allJars.get().forEach { file ->
            println("handling " + file.asFile.absolutePath)
            val jarFile = JarFile(file.asFile)
            jarFile.entries().iterator().forEach { jarEntry ->
                println("Adding from jar ${jarEntry.name}")
                jarOutput.writeEntity(jarEntry.name, jarFile.getInputStream(jarEntry))
            }
            jarFile.close()
        }

        allDirectories.get().forEach { directory ->
            println("handling " + directory.asFile.absolutePath)
            directory.asFile.walk().forEach { file ->

                fun inBlackList(className: String): Boolean {
                    return apmExtension.get().blackList.contains(className.replace("/".toRegex(), "."))
                }

                fun default() {
                    val relativePath = directory.asFile.toURI().relativize(file.toURI()).path
                    jarOutput.writeEntity(relativePath.replace(File.separatorChar, '/'), file.inputStream())
                }

                fun refactor(classWriter: ClassWriter) {
                    val relativePath = directory.asFile.toURI().relativize(file.toURI()).path
                    jarOutput.writeEntity(relativePath.replace(File.separatorChar, '/'), classWriter.toByteArray())
                }

                if (file.isFile) {
                    if (file.path.endsWith("App.class")) {
                        file.inputStream().use {
                            val classReader = ClassReader(it)
                            if (inBlackList(classReader.className)) {
                                default()
                            } else {
                                val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
                                val classVisitor = EHomeApplicationClassVisitor(classWriter)
                                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)

                                refactor(classWriter)
                            }

                        }
                    } else if (file.path.endsWith("Activity.class")) {
                        file.inputStream().use {
                            val classReader = ClassReader(it)
                            if (inBlackList(classReader.className)) {
                                default()
                            } else {
                                val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
                                val classVisitor = EHomeActivityClassVisitor(classWriter)
                                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)

                                refactor(classWriter)
                            }
                        }
                    } else {
                        default()
                    }
                }
            }
        }

        jarOutput.close()
    }


    private fun JarOutputStream.writeEntity(name: String, inputStream: InputStream) {
        if (jarPaths.contains(name)) {
            printDuplicatedMessage(name)
        } else {
            putNextEntry(JarEntry(name))
            inputStream.copyTo(this)
            closeEntry()
            jarPaths.add(name)
        }
    }

    private fun JarOutputStream.writeEntity(relativePath: String, byteArray: ByteArray) {
        if (jarPaths.contains(relativePath)) {
            printDuplicatedMessage(relativePath)
        } else {
            putNextEntry(JarEntry(relativePath))
            write(byteArray)
            closeEntry()
            jarPaths.add(relativePath)
        }
    }

    private fun printDuplicatedMessage(name: String) = println("Cannot add ${name}, because output Jar already has file with the same name.")
}